#include "mesh.h"
#include "texture.h"
#include "material.h"



c_d3dx_submesh::c_d3dx_submesh(c_d3dx_mesh* parent_mesh, D3DXATTRIBUTERANGE *att_entry, const material_ptr& mat)
    : m_att_id(att_entry->AttribId)
    , m_face_start(att_entry->FaceStart)
    , m_face_count(att_entry->FaceCount)
    , m_vertex_start(att_entry->VertexStart)
    , m_vertex_count(att_entry->VertexCount)
    , m_parent_mesh(parent_mesh)
    , m_material(mat)
{    
}

c_d3dx_submesh::~c_d3dx_submesh()
{
	m_parent_mesh = NULL; 
}

void c_d3dx_submesh::get_render_op(render_op& op)
{
    op.type = D3DPT_TRIANGLELIST; 
    op.base_vertex_index = 0; 
    op.min_index = m_vertex_start;
    op.num_vertices =  m_vertex_count;
    op.start_index = m_face_start * 3; 
    op.num_primitives = m_face_count; 
}

//////////////////////////////////////////////////////////////////////////

c_d3dx_mesh::c_d3dx_mesh(const d3dx_mesh_ptr& d3dx_mesh)
    : m_d3dx_mesh(d3dx_mesh)
{
    unsigned long num_submeshes = 0;
    m_d3dx_mesh->GetAttributeTable(NULL, &num_submeshes);
    
    typedef boost::scoped_array<D3DXATTRIBUTERANGE> att_entries_array;
    att_entries_array arr(new D3DXATTRIBUTERANGE[num_submeshes]);
    m_d3dx_mesh->GetAttributeTable(arr.get(), &num_submeshes); 

    m_submeshes.reserve(num_submeshes); 
    for (unsigned int i = 0; i < num_submeshes; ++i) {
        submesh_ptr submesh_ptr(new c_d3dx_submesh(this, &arr[i], make_default_material())); 
        m_submeshes.push_back(submesh_ptr);
    }
}

c_d3dx_mesh::~c_d3dx_mesh()
{
}

d3d9_vb_ptr c_d3dx_mesh::get_d3d9_vb_ptr()
{
    d3d9_vb_ptr d3d9_vb; 
    m_d3dx_mesh->GetVertexBuffer(&d3d9_vb);
    return d3d9_vb;
}

d3d9_ib_ptr c_d3dx_mesh::get_d3d9_ib_ptr()
{
    d3d9_ib_ptr d3d9_ib;
    m_d3dx_mesh->GetIndexBuffer(&d3d9_ib); 
    return d3d9_ib;
}

mesh_ptr create_mesh_from_x_file(const std::string& file_name)
{
    HRESULT hr; 
    d3dx_mesh_ptr p_d3dx_mesh;
    d3dx_mesh_ptr p_d3dx_mesh_opt; 
    d3dx_buffer_ptr p_adjacency_buf;
    d3dx_buffer_ptr p_materials_buf; 
    d3dx_buffer_ptr p_effects_buf;
    d3d9_device_ptr d3d9_device = c_render_device9::get_singleton().get_d3d_device_ptr();

    unsigned long num_materials = 0;
        
    V(D3DXLoadMeshFromXA(
        file_name.c_str(),
        D3DXMESH_MANAGED,
        d3d9_device, 
        &p_adjacency_buf,
        &p_materials_buf,
        &p_effects_buf,
        &num_materials,
        &p_d3dx_mesh)); 
    
    BOOST_ASSERT(p_adjacency_buf);
    BOOST_ASSERT(p_materials_buf); 
    BOOST_ASSERT(p_materials_buf);
    BOOST_ASSERT(p_effects_buf);
    BOOST_ASSERT(p_d3dx_mesh); 
    
    p_d3dx_mesh->Optimize(
        D3DXMESHOPT_ATTRSORT | D3DXMESH_MANAGED,
        NULL,
        NULL,
        NULL,
        NULL,
        &p_d3dx_mesh_opt
        );
    
    mesh_ptr mesh(new c_d3dx_mesh(p_d3dx_mesh_opt));
    return mesh;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// define vertex format
struct cube_vertex 
{
    D3DXVECTOR3 pos; 
    D3DXVECTOR3 normal;
    D3DXVECTOR2 texcoord; 

    static const unsigned long fvf = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;

    cube_vertex(const D3DXVECTOR3& _pos, const D3DXVECTOR3& _normal, const D3DXVECTOR2& _texcoord)
        : pos(_pos)
        , normal(_normal)
        , texcoord(_texcoord) 
    {}
}; 

mesh_ptr make_simple_cube_mesh(float width, float height, float depth)
{
    HRESULT hr; 
    d3d9_device_ptr device = c_render_device9::get_singleton().get_d3d_device_ptr(); 
    d3dx_mesh_ptr temp_mesh;

    V(D3DXCreateMeshFVF(
        12,                // number of faces
        8,                 // number of vertices
        D3DXMESH_MANAGED,
        cube_vertex::fvf, 
        device, 
        &temp_mesh));
    
    // vertex data
    float half_width = width / 2; 
    float half_height = height / 2; 
    float half_depth = depth / 2;

    cube_vertex *vertices = NULL; 
    V(temp_mesh->LockVertexBuffer(0, (void**)&vertices)); 
    vertices[0] = cube_vertex(D3DXVECTOR3(half_width, half_height, -half_depth), D3DXVECTOR3(0.333333f,0.666667f,-0.666667f), D3DXVECTOR2(0.0f, 1.0f)); 
    vertices[1] = cube_vertex(D3DXVECTOR3(-half_width, half_height, -half_depth), D3DXVECTOR3(-0.816497f,0.408248f,-0.408248f), D3DXVECTOR2(1.0f, 1.0f));
    vertices[2] = cube_vertex(D3DXVECTOR3(-half_width, half_height, half_depth), D3DXVECTOR3(-0.333333f,0.666667f,0.666667f), D3DXVECTOR2(0.0f, 1.0f));
    vertices[3] = cube_vertex(D3DXVECTOR3(half_width, half_height, half_depth), D3DXVECTOR3(0.816497f,0.408248f,0.408248f), D3DXVECTOR2(1.0f, 1.0f)); 
    vertices[4] = cube_vertex(D3DXVECTOR3(half_width, -half_height, -half_depth), D3DXVECTOR3(0.666667f,-0.666667f,-0.333333f), D3DXVECTOR2(0.0f, 0.0f));
    vertices[5] = cube_vertex(D3DXVECTOR3(-half_width, -half_height, -half_depth), D3DXVECTOR3(-0.408248f,-0.408248f,-0.816497f), D3DXVECTOR2(1.0f, 0.0f));
    vertices[6] = cube_vertex(D3DXVECTOR3(-half_width, -half_height, half_depth), D3DXVECTOR3(-0.666667f,-0.666667f,0.333333f), D3DXVECTOR2(0.0f, 0.0f));
    vertices[7] = cube_vertex(D3DXVECTOR3(half_width, -half_height, half_depth), D3DXVECTOR3(0.408248f,-0.408248f,0.816497f), D3DXVECTOR2(1.0f, 0.0f));
    V(temp_mesh->UnlockVertexBuffer()); 
    //
    //// index data
    unsigned short *indices = NULL; 
    V(temp_mesh->LockIndexBuffer(0, (void**)&indices));
    unsigned int face_idx = 0; 
    indices[face_idx * 3 + 0] = 0;
    indices[face_idx * 3 + 1] = 1;
    indices[face_idx * 3 + 2] = 2;
    face_idx++; 
    indices[face_idx * 3 + 0] = 0;
    indices[face_idx * 3 + 1] = 2;
    indices[face_idx * 3 + 2] = 3;
    face_idx++; 
    indices[face_idx * 3 + 0] = 0;
    indices[face_idx * 3 + 1] = 4;
    indices[face_idx * 3 + 2] = 5;
    face_idx++; 
    indices[face_idx * 3 + 0] = 0;
    indices[face_idx * 3 + 1] = 5;
    indices[face_idx * 3 + 2] = 1;
    face_idx++; 
    indices[face_idx * 3 + 0] = 1;
    indices[face_idx * 3 + 1] = 5;
    indices[face_idx * 3 + 2] = 6;
    face_idx++; 
    indices[face_idx * 3 + 0] = 1;
    indices[face_idx * 3 + 1] = 6;
    indices[face_idx * 3 + 2] = 2;
    face_idx++; 
    indices[face_idx * 3 + 0] = 2;
    indices[face_idx * 3 + 1] = 6;
    indices[face_idx * 3 + 2] = 7;
    face_idx++; 
    indices[face_idx * 3 + 0] = 2;
    indices[face_idx * 3 + 1] = 7;
    indices[face_idx * 3 + 2] = 3;
    face_idx++; 
    indices[face_idx * 3 + 0] = 3;
    indices[face_idx * 3 + 1] = 7;
    indices[face_idx * 3 + 2] = 4;
    face_idx++; 
    indices[face_idx * 3 + 0] = 3;
    indices[face_idx * 3 + 1] = 4;
    indices[face_idx * 3 + 2] = 0;
    face_idx++; 
    indices[face_idx * 3 + 0] = 4;
    indices[face_idx * 3 + 1] = 7;
    indices[face_idx * 3 + 2] = 6;
    face_idx++; 
    indices[face_idx * 3 + 0] = 4;
    indices[face_idx * 3 + 1] = 6;
    indices[face_idx * 3 + 2] = 5;
    V(temp_mesh->UnlockIndexBuffer()); 
    
    // adjacency
    unsigned int num_faces = temp_mesh->GetNumFaces(); 
    std::vector<DWORD> adj_info(num_faces * 3);
    temp_mesh->GenerateAdjacency(0.0f, &adj_info[0]); 
    
    // attributes table
    unsigned long *p_attribs = NULL;
    V(temp_mesh->LockAttributeBuffer(0, &p_attribs));
    for (unsigned int i = 0; i < 12; ++i)
        *p_attribs++ = 0; 
    V(temp_mesh->UnlockAttributeBuffer());
    
    d3dx_mesh_ptr mesh_opt; 
    temp_mesh->Optimize(
        D3DXMESHOPT_ATTRSORT | D3DXMESH_MANAGED,
        NULL,
        NULL,
        NULL,
        NULL,
        &mesh_opt
        );

    mesh_ptr new_mesh(new c_d3dx_mesh(mesh_opt));
    
    return new_mesh; 
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void set_face_texcoord(cube_vertex *pv, float u, float v)
{
    ( pv++ )->texcoord = D3DXVECTOR2( 0.0f, 0.0f );
    ( pv++ )->texcoord = D3DXVECTOR2( u, 0.0f );
    ( pv++ )->texcoord = D3DXVECTOR2( u, v );
    ( pv++ )->texcoord = D3DXVECTOR2( 0.0f, v );
}

mesh_ptr make_room_mesh(float width, float height, float depth)
{
    HRESULT hr; 
    d3d9_device_ptr device = c_render_device9::get_singleton().get_d3d_device_ptr(); 
    d3dx_mesh_ptr temp_mesh;

    V(D3DXCreateMeshFVF(
        12,                // number of faces
        24,                // number of vertices
        D3DXMESH_MANAGED,
        cube_vertex::fvf, 
        device, 
        &temp_mesh));

    cube_vertex *vertices = NULL; 
    V(temp_mesh->LockVertexBuffer(0, (void**)&vertices)); 
    
    cube_vertex *pv = vertices; 

    // Front wall
    set_face_texcoord( pv, 7.0f, 2.0f );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, height, depth );
    ( pv++ )->pos = D3DXVECTOR3( width, height, depth );
    ( pv++ )->pos = D3DXVECTOR3( width, 0.0f, depth );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, 0.0f, depth );

    // Right wall
    set_face_texcoord( pv, 10.5f, 2.0f );
    ( pv++ )->pos = D3DXVECTOR3( width, height, depth );
    ( pv++ )->pos = D3DXVECTOR3( width, height, 0.0f );
    ( pv++ )->pos = D3DXVECTOR3( width, 0.0f, 0.0f );
    ( pv++ )->pos = D3DXVECTOR3( width, 0.0f, depth );

    // Back wall
    set_face_texcoord( pv, 7.0f, 2.0f );
    ( pv++ )->pos = D3DXVECTOR3( width, height, 0.0f );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, height, 0.0f );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
    ( pv++ )->pos = D3DXVECTOR3( width, 0.0f, 0.0f );

    // Left wall
    set_face_texcoord( pv, 10.5f, 2.0f );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, height, 0.0f );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, height, depth );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, 0.0f, depth );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
    
    // Floor
    set_face_texcoord( pv, 7.0f, 7.0f );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, 0.0f, depth );
    ( pv++ )->pos = D3DXVECTOR3( width, 0.0f, depth );
    ( pv++ )->pos = D3DXVECTOR3( width, 0.0f, 0.0f );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );

    // Ceiling
    set_face_texcoord( pv, 7.0f, 2.0f );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, height, 0.0f );
    ( pv++ )->pos = D3DXVECTOR3( width, height, 0.0f );
    ( pv++ )->pos = D3DXVECTOR3( width, height, depth );
    ( pv++ )->pos = D3DXVECTOR3( 0.0f, height, depth );

    V(temp_mesh->UnlockVertexBuffer()); 
    
    // compute normals
    D3DXComputeNormals(temp_mesh, NULL);
    
    //// index data
    unsigned short *indices = NULL; 
    V(temp_mesh->LockIndexBuffer(0, (void**)&indices));

    for( unsigned int i = 0; i < temp_mesh->GetNumFaces() / 2; i++ )
    {
        *indices++ = (unsigned short)( ( i * 4 ) + 0 );
        *indices++ = (unsigned short)( ( i * 4 ) + 1 );
        *indices++ = (unsigned short)( ( i * 4 ) + 2 );
        *indices++ = (unsigned short)( ( i * 4 ) + 0 );
        *indices++ = (unsigned short)( ( i * 4 ) + 2 );
        *indices++ = (unsigned short)( ( i * 4 ) + 3 );
    }

    temp_mesh->UnlockIndexBuffer();

    // adjacency
    unsigned int num_faces = temp_mesh->GetNumFaces(); 
    std::vector<DWORD> adj_info(num_faces * 3);
    temp_mesh->GenerateAdjacency(0.0f, &adj_info[0]); 
    
    // Set attribute groups to draw floor, ceiling, walls, and paintings
    // separately, with different shader constants. These group numbers
    // will be used during the calls to DrawSubset().
    unsigned long *p_attribs = NULL;
    V(temp_mesh->LockAttributeBuffer( 0, &p_attribs)); 
    for( unsigned int i = 0; i < 8; i++ )
        *p_attribs++ = 0;
    for( unsigned int i = 0; i < 2; i++ ) 
        *p_attribs++ = 1;
    for( unsigned int i = 0; i < 2; i++ ) 
        *p_attribs++ = 2;
    V(temp_mesh->UnlockAttributeBuffer());
    
    d3dx_mesh_ptr mesh_opt; 
    temp_mesh->Optimize(
        D3DXMESHOPT_ATTRSORT | D3DXMESH_MANAGED,
        NULL,
        NULL,
        NULL,
        NULL,
        &mesh_opt
        );

    mesh_ptr new_mesh(new c_d3dx_mesh(mesh_opt));
    
    return new_mesh;
}

